/*
	This file is part of the OdinMS Maple Story Server
    Copyright (C) 2008 ~ 2010 Patrick Huy <patrick.huy@frz.cc> 
                       Matthias Butz <matze@odinms.de>
                       Jan Christian Meyer <vimes@odinms.de>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License version 3
    as published by the Free Software Foundation. You may not use, modify
    or distribute this program under any other version of the
    GNU Affero General Public License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package handling.login.handler;

import java.util.List;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Calendar;

import client.IItem;
import client.Item;
import client.LoginCrypto;
import client.MapleClient;
import client.MapleCharacter;
import client.MapleCharacterUtil;
import client.MapleInventory;
import client.MapleInventoryType;
import client.MapleQuestStatus;
import handling.channel.ChannelServer;
import handling.login.LoginInformationProvider;
import handling.login.LoginServer;
import handling.login.LoginWorker;
import server.quest.MapleQuest;
import tools.MaplePacketCreator;
import tools.packet.LoginPacket;
import tools.KoreanDateUtil;
import tools.data.input.SeekableLittleEndianAccessor;

public class CharLoginHandler {

    private static final boolean loginFailCount(final MapleClient c) {
	c.loginAttempt++;
	if (c.loginAttempt > 5) {
	    return true;
	}
	return false;
    }

    public static final void login(final SeekableLittleEndianAccessor slea, final MapleClient c) {
	final String login = slea.readMapleAsciiString();
	final String pwd = LoginCrypto.decryptRSA(slea.readMapleAsciiString());

	c.setAccountName(login);
	final boolean ipBan = c.hasBannedIP();
	final boolean macBan = false; // MSEA doesn't sent mac
//	final boolean macBan = c.hasBannedMac();

	int loginok = c.login(login, pwd, ipBan || macBan);
	final Calendar tempbannedTill = c.getTempBanCalendar();

	if (loginok == 0 && (ipBan || macBan)) {
	    loginok = 3;
	    if (macBan) {
		// this is only an ipban o.O" - maybe we should refactor this a bit so it's more readable
		MapleCharacter.ban(c.getSession().getRemoteAddress().toString().split(":")[0], "Enforcing account ban, account " + login, false);
	    }
	}
	if (loginok != 0) {
	    if (!loginFailCount(c)) {
		c.getSession().write(LoginPacket.getLoginFailed(loginok));
	    }
	} else if (tempbannedTill.getTimeInMillis() != 0) {
	    if (!loginFailCount(c)) {
		c.getSession().write(LoginPacket.getTempBan(KoreanDateUtil.getTempBanTimestamp(tempbannedTill.getTimeInMillis()), c.getBanReason()));
	    }
	} else {
	    c.loginAttempt = 0;
	    LoginWorker.registerClient(c);
	}
    }

    public static final void ServerListRequest(final MapleClient c) {
	final LoginServer ls = LoginServer.getInstance();
	c.getSession().write(LoginPacket.getServerList(6, ls.getServerName(), ls.getLoad()));
	//c.getSession().write(MaplePacketCreator.getServerList(1, "Scania", LoginServer.getInstance().getChannels(), 1200));
	//c.getSession().write(MaplePacketCreator.getServerList(2, "Scania", LoginServer.getInstance().getChannels(), 1200));
	//c.getSession().write(MaplePacketCreator.getServerList(3, "Scania", LoginServer.getInstance().getChannels(), 1200));
	c.getSession().write(LoginPacket.getEndOfServerList());
    }

    public static final void ServerStatusRequest(final MapleClient c) {
	// 0 = Select world normally
	// 1 = "Since there are many users, you may encounter some..."
	// 2 = "The concurrent users in this world have reached the max"

	int numPlayer = 0;
	for (ChannelServer cserv : ChannelServer.getAllInstances()) {
	    numPlayer += cserv.getPlayerStorage().getConnectedClients();
	}
	final int userLimit = LoginServer.getInstance().getUserLimit();
	if (numPlayer >= userLimit) {
	    c.getSession().write(LoginPacket.getServerStatus(2));
	} else if (numPlayer * 2 >= userLimit) {
	    c.getSession().write(LoginPacket.getServerStatus(1));
	} else {
	    c.getSession().write(LoginPacket.getServerStatus(0));
	}
    }

    public static final void CharlistRequest(final SeekableLittleEndianAccessor slea, final MapleClient c) {
	final int server = slea.readByte();
	final int channel = slea.readByte() + 1;

	c.setWorld(server);
	System.out.println("Client is connecting to server " + server + " channel " + channel + "");
	c.setChannel(channel);

	final List<MapleCharacter> chars = c.loadCharacters(server);
	if (chars != null) {
	    c.getSession().write(LoginPacket.getCharList(c.getSecondPassword() != null, chars));
	} else {
	    c.getSession().close();
	}
    }

    public static final void CheckCharName(final String name, final MapleClient c) {
	c.getSession().write(LoginPacket.charNameResponse(name,
		!MapleCharacterUtil.canCreateChar(name) || LoginInformationProvider.getInstance().isForbiddenName(name)));
    }

    public static final void CreateChar(final SeekableLittleEndianAccessor slea, final MapleClient c) {
	final String name = slea.readMapleAsciiString();
	final int JobType = slea.readInt(); // 1 = Adventurer, 0 = Cygnus, 2 = Aran
	final int face = slea.readInt();
	final int hair = slea.readInt();
	final int hairColor = slea.readInt();
	final int skinColor = slea.readInt();
	final int top = slea.readInt();
	final int bottom = slea.readInt();
	final int shoes = slea.readInt();
	final int weapon = slea.readInt();

	final byte gender = c.getGender();

	if (JobType == 0 || JobType == 1) { // Advanturer and Cygnus
	    if (gender == 0) {
		if (face != 20000 && face != 20001 && face != 20002) {
		    return;
		}
		if (hair != 30000 && hair != 30020 && hair != 30030) {
		    return;
		}
		if (top != 1040002 && top != 1040006 && top != 1040010) {
		    return;
		}
		if (bottom != 1060006 && bottom != 1060002) {
		    return;
		}
	    } else if (gender == 1) {
		if (face != 21000 && face != 21001 && face != 21002) {
		    return;
		}
		if (hair != 31000 && hair != 31040 && hair != 31050) {
		    return;
		}
		if (top != 1041002 && top != 1041006 && top != 1041010 && top != 1041011) {
		    return;
		}
		if (bottom != 1061002 && bottom != 1061008) {
		    return;
		}
	    } else {
		return;
	    }
	    if (skinColor < 0 || skinColor > 3) {
		return;
	    }
	    if (weapon != 1302000 && weapon != 1322005 && weapon != 1312004) {
		return;
	    }
	    if (shoes != 1072001 && shoes != 1072005 && shoes != 1072037 && shoes != 1072038) {
		return;
	    }
	    if (hairColor != 0 && hairColor != 2 && hairColor != 3 && hairColor != 7) {
		return;
	    }
	} else { // Aran
	    if (gender == 0) { // Male
		if (hair != 30000 && hair != 30020 && hair != 30030) {
		    return;
		}
		if (face != 20100 && face != 20401 && face != 20402) {
		    return;
		}
	    } else { // Female
		if (hair != 31000 && hair != 31040 && hair != 31050) {
		    return;
		}
		if (face != 21700 && face != 21201 && face != 21002) {
		    return;
		}
	    }
	    if (top != 1042167 || bottom != 1062115 || weapon != 1442079 || shoes != 1072383) {
		return;
	    }
	}

	MapleCharacter newchar = MapleCharacter.getDefault(c, JobType);
	newchar.setWorld(c.getWorld());
	newchar.setFace(face);
	newchar.setHair(hair + hairColor);
	newchar.setGender(gender);
	newchar.setName(name);
	newchar.setSkinColor(skinColor);

	MapleInventory equip = newchar.getInventory(MapleInventoryType.EQUIPPED);
	final LoginInformationProvider li = LoginInformationProvider.getInstance();

	IItem item = li.getEquipById(top);
	item.setPosition((byte) -5);
	equip.addFromDB(item);

	item = li.getEquipById(bottom);
	item.setPosition((byte) -6);
	equip.addFromDB(item);

	item = li.getEquipById(shoes);
	item.setPosition((byte) -7);
	equip.addFromDB(item);

	item = li.getEquipById(weapon);
	item.setPosition((byte) -11);
	equip.addFromDB(item);

	newchar.getInventory(MapleInventoryType.ETC).addItem(new Item(JobType == 1 ? 4161001 : JobType == 0 ? 4161047 : 4161048, (byte) 0, (short) 1, (byte) 0));

	switch (JobType) {
	    case 0: // Cygnus
		final MapleQuestStatus status = newchar.getQuestNAdd(MapleQuest.getInstance(20022));
		status.setStatus((byte) 1);
		status.setCustomData("1"); // Cygnus quest

		newchar.getInventory(MapleInventoryType.ETC).addItem(new Item(4161047, (byte) 0, (short) 1, (byte) 0));
		break;
	    case 1: // Adventurer
		newchar.getInventory(MapleInventoryType.ETC).addItem(new Item(4161001, (byte) 0, (short) 1, (byte) 0));
		break;
	    case 2: // Aran
		newchar.getInventory(MapleInventoryType.ETC).addItem(new Item(4161048, (byte) 0, (short) 1, (byte) 0));
		break;
	}

	if (MapleCharacterUtil.canCreateChar(name) && !li.isForbiddenName(name)) {
	    MapleCharacter.saveNewCharToDB(newchar, JobType);
	    c.getSession().write(LoginPacket.addNewCharEntry(newchar, true));
	    c.createdChar(newchar.getId());
	} else {
	    c.getSession().write(LoginPacket.addNewCharEntry(newchar, false));
	}
    }

    public static final void DeleteChar(final SeekableLittleEndianAccessor slea, final MapleClient c) {
	String Secondpw_Client = null;
	if (slea.readByte() > 0) { // Specific if user have second password or not
	    Secondpw_Client = slea.readMapleAsciiString();
	}
	final String AS13Digit = slea.readMapleAsciiString();
	final int Character_ID = slea.readInt();

	if (!c.login_Auth(Character_ID)) {
	    c.getSession().close();
	    return; // Attempting to delete other character
	}
	byte state = 0;

	if (c.getSecondPassword() != null) { // On the server, there's a second password
	    if (Secondpw_Client == null) { // Client's hacking
		c.getSession().close();
		return;
	    } else {
		if (!c.CheckSecondPassword(Secondpw_Client)) { // Wrong Password
		    state = 12;
		}
	    }
	}
	// TODO, implement 13 digit Asiasoft passport too.

	if (state == 0) {
	    if (!c.deleteCharacter(Character_ID)) {
		state = 1; //actually something else would be good o.o
	    }
	}
	c.getSession().write(LoginPacket.deleteCharResponse(Character_ID, state));
    }

    public static final void Character_WithoutSecondPassword(final SeekableLittleEndianAccessor slea, final MapleClient c) {
	slea.skip(1);
	final int charId = slea.readInt();
	final String currentpw = c.getSecondPassword();

	if (slea.available() != 0) {
	    if (currentpw != null) { // Hack
		c.getSession().close();
		return;
	    }
	    final String setpassword = slea.readMapleAsciiString();

	    if (setpassword.length() >= 4 && setpassword.length() <= 16) {
		c.setSecondPassword(setpassword);
		c.updateSecondPassword();

		if (!c.login_Auth(charId)) {
		    c.getSession().close();
		    return;
		}
	    } else {
		c.getSession().write(LoginPacket.secondPwError((byte) 14));
		return;
	    }
	} else if (loginFailCount(c) || currentpw != null || !c.login_Auth(charId)) {
	    c.getSession().close();
	    return;
	}
	try {
	    if (c.getIdleTask() != null) {
		c.getIdleTask().cancel(true);
	    }
	    c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION, c.getSessionIPAddress());

	    final String channelServerIP = MapleClient.getChannelServerIPFromSubnet(c.getSession().getRemoteAddress().toString().replace("/", "").split(":")[0], c.getChannel());
	    if (channelServerIP.equals("0.0.0.0")) {
		final String[] socket = LoginServer.getInstance().getIP(c.getChannel()).split(":");
		c.getSession().write(MaplePacketCreator.getServerIP(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1]), charId));
	    } else {
		final String[] socket = LoginServer.getInstance().getIP(c.getChannel()).split(":");
		c.getSession().write(MaplePacketCreator.getServerIP(InetAddress.getByName(channelServerIP), Integer.parseInt(socket[1]), charId));
	    }
	} catch (UnknownHostException e) {
	    System.err.println("Host not found" + e);
	}
    }

    public static final void Character_WithSecondPassword(final SeekableLittleEndianAccessor slea, final MapleClient c) {
	final String password = slea.readMapleAsciiString();
	final int charId = slea.readInt();

	if (loginFailCount(c) || c.getSecondPassword() == null || !c.login_Auth(charId)) { // This should not happen unless player is hacking
	    c.getSession().close();
	    return;
	}

	if (c.CheckSecondPassword(password)) {
	    try {
		if (c.getIdleTask() != null) {
		    c.getIdleTask().cancel(true);
		}
		c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION, c.getSessionIPAddress());

		final String channelServerIP = MapleClient.getChannelServerIPFromSubnet(c.getSession().getRemoteAddress().toString().replace("/", "").split(":")[0], c.getChannel());
		if (channelServerIP.equals("0.0.0.0")) {
		    final String[] socket = LoginServer.getInstance().getIP(c.getChannel()).split(":");
		    c.getSession().write(MaplePacketCreator.getServerIP(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1]), charId));
		} else {
		    final String[] socket = LoginServer.getInstance().getIP(c.getChannel()).split(":");
		    c.getSession().write(MaplePacketCreator.getServerIP(InetAddress.getByName(channelServerIP), Integer.parseInt(socket[1]), charId));
		}
	    } catch (UnknownHostException e) {
		System.err.println("Host not found" + e);
	    }
	} else {
	    c.getSession().write(LoginPacket.secondPwError((byte) 14));
	}
    }
}
